﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using DDD;

namespace DevApp {
    public class Program5 : GameWindow {

        World wld;
        Graphics3D g3d;


        public Program5 ()
            : base (640, 480,
              new GraphicsMode (), "DDD: Skinned Animation", 0,
              DisplayDevice.Default, 3, 0,
              GraphicsContextFlags.ForwardCompatible | GraphicsContextFlags.Debug) {
        }

        private Appearance CreateAppearance () {
            var vert = new VertexShader ("Shaders/simple.vert");
            var frag = new FragmentShader ("Shaders/simple.frag");
            var sp = new ShaderProgram (vert, frag);

            var unifs1 = new UniformArray ();
            unifs1.Add ("ModelViewMatrix", UniformArray.CurrentNode, () => ((Node)null).LocalToCamera);
            unifs1.Add ("ProjectionMatrix", UniformArray.CurrentActiveCamera, () => ((Camera)null).ProjectionMatrix);

            var unifs2 = new UniformArray ();
            unifs2.Add ("Diffuse", UniformArray.CurrentMaterial, () => ((PhongMaterial)null).Diffuse);

            var mat = new PhongMaterial ();
            mat.SetDiffuse (1, 1, 1, 1);

            var app = new Appearance ();
            app.SetShaderProgram (sp);
            app.AddUniformArray (unifs1);
            app.AddUniformArray (unifs2);
            app.SetMaterial (mat);

            return app;
        }

        private Appearance CreateTransformAppearance (SkinnedMesh msh) {
            var vert = new VertexShader ("Shaders/bone-transform.vert");
            var frag = new FragmentShader ("Shaders/simple.frag");
            var sp = new ShaderProgram (vert, frag);
            sp.SetTransformFeedbackEnable (true, 2);
            sp.SetFeedbackVarying (0, "f_Position");
            sp.SetFeedbackVarying (1, "f_Normal");


            var unifs1 = new UniformArray ();
            unifs1.Add ("ModelViewMatrix", UniformArray.CurrentNode, () => ((Node)null).LocalToCamera);
            unifs1.Add ("ProjectionMatrix", UniformArray.CurrentActiveCamera, () => ((Camera)null).ProjectionMatrix);

            var unifs2 = new UniformArray ();
            unifs2.Add ("SkinningMatrix", UniformArray.CurrentMaterial, () => ((BoneTransformMaterial)null).SkinningMatrices);
            unifs2.Add ("NormalSkinningMatrix", UniformArray.CurrentMaterial, () => ((BoneTransformMaterial)null).NormalSkinningMatrices);

            var mat = new BoneTransformMaterial ();
            mat.SetSkinnedMesh (msh);

            var app = new Appearance ();
            app.SetShaderProgram (sp);
            app.AddUniformArray (unifs1);
            app.AddUniformArray (unifs2);
            app.SetMaterial (mat);

            return app;
        }

        private SkinnedMesh CreateSkinnedMesh () {

            var positions = new float[]{-1, 0, 0, 1, 0, 0,
                                        -1, 1, 0, 1, 1, 0,
                                        -1, 2, 0, 1, 2, 0,
                                        -1, 3, 0, 1, 3, 0,
                                        -1, 4, 0, 1, 4, 0,
                                        -1, 5, 0, 1, 5, 0,
                                        -1, 6, 0, 1, 6, 0,
                                        -1, 7, 0, 1, 7, 0,
                                        -1, 8, 0, 1, 8, 0,
                                        -1, 9, 0, 1, 9, 0,
                                        -1, 10, 0, 1, 10, 0};
            var normals = new float[]{0,0,1, 0,0,1,
                                      0,0,1, 0,0,1,
                                      0,0,1, 0,0,1,
                                      0,0,1, 0,0,1,
                                      0,0,1, 0,0,1,
                                      0,0,1, 0,0,1,
                                      0,0,1, 0,0,1,
                                      0,0,1, 0,0,1,
                                      0,0,1, 0,0,1,
                                      0,0,1, 0,0,1,
                                      0,0,1, 0,0,1};
            var boneIndices = new int[]{0,1,-1,-1,   0,1,-1,-1,
                                        0,1,-1,-1,   0,1,-1,-1,
                                        0,1,-1,-1,   0,1,-1,-1,
                                        0,1,-1,-1,   0,1,-1,-1,
                                        0,1,-1,-1,   0,1,-1,-1,
                                        0,1,-1,-1,   0,1,-1,-1,
                                        0,1,-1,-1,   0,1,-1,-1,
                                        0,1,-1,-1,   0,1,-1,-1,
                                        0,1,-1,-1,   0,1,-1,-1,
                                        0,1,-1,-1,   0,1,-1,-1,
                                        0,1,-1,-1,   0,1,-1,-1};
            var boneWeights = new float[]{1.0f, 0.0f, 0.0f, 0.0f,   1.0f, 0.0f, 0.0f, 0.0f,
                                          1.0f, 0.0f, 0.0f, 0.0f,   1.0f, 0.0f, 0.0f, 0.0f,
                                          1.0f, 0.0f, 0.0f, 0.0f,   1.0f, 0.0f, 0.0f, 0.0f,
                                          1.0f, 0.0f, 0.0f, 0.0f,   1.0f, 0.0f, 0.0f, 0.0f,
                                          0.75f, 0.25f, 0.0f, 0.0f,   0.75f, 0.25f, 0.0f, 0.0f,
                                          0.50f, 0.50f, 0.0f, 0.0f,   0.50f, 0.50f, 0.0f, 0.0f,
                                          0.25f, 0.75f, 0.0f, 0.0f,   0.25f, 0.75f, 0.0f, 0.0f,
                                          0.0f, 1.0f, 0.0f, 0.0f,   0.0f, 1.0f, 0.0f, 0.0f,
                                          0.0f, 1.0f, 0.0f, 0.0f,   0.0f, 1.0f, 0.0f, 0.0f,
                                          0.0f, 1.0f, 0.0f, 0.0f,   0.0f, 1.0f, 0.0f, 0.0f,
                                          0.0f, 1.0f, 0.0f, 0.0f,   0.0f, 1.0f, 0.0f, 0.0f,};


            var bindPosArray = new VertexArray<float> (22, 3);
            var transPosArray = new VertexArray<float> (22, 3);
            var bindNormArray = new VertexArray<float> (22, 3);
            var transNormArray = new VertexArray<float> (22, 3);
            var boneIndexArray = new VertexArray<int> (22, 4);
            var boneWeightArray = new VertexArray<float> (22, 4);
            bindPosArray.Set (0, 22, positions);
            transPosArray.Set (0, 22, positions);
            bindNormArray.Set (0, 22, normals);
            transNormArray.Set (0, 22, normals);
            boneIndexArray.Set (0, 22, boneIndices);
            boneWeightArray.Set (0, 22, boneWeights);


            var indices = new int[]{0,1,2, 2,1,3,
                                    2,3,4, 4,3,5,
                                    4,5,6, 6,5,7,
                                    6,7,8, 8,7,9,
                                    8,9,10, 10,9,11,
                                    10,11,12, 12,11,13,
                                    12,13,14, 14,13,15,
                                    14,15,16, 16,15,17,
                                    16,17,18, 18,17,19,
                                    18,19,20, 20,19,21,
                                    20,21,22, 22,21,23};

            var ibuf = new IndexBuffer<int> (PrimitiveType.Triangle, 20);
            ibuf.Set (0, 20, indices);

            var vbuf = new VertexBuffer ();
            vbuf.AddVertexArray ("v_Position", transPosArray);
            vbuf.AddVertexArray ("v_Normal", transNormArray);

            var fbuf = new FeedbackBuffer ();
            fbuf.AddVertexArray ("f_Position", transPosArray, null);
            fbuf.AddVertexArray ("f_Normal", transNormArray, null);

            var tvbuf = new VertexBuffer ();
            tvbuf.AddVertexArray ("v_Position", bindPosArray);
            tvbuf.AddVertexArray ("v_Normal", bindNormArray);
            tvbuf.AddVertexArray ("v_BoneIndex", boneIndexArray);
            tvbuf.AddVertexArray ("v_BoneWeight", boneWeightArray);


            var msh = new SkinnedMesh ();
            msh.SetVertexBuffer (vbuf);
            //msh.SetIndexBuffer (ibuf);
            msh.SetAppearance (CreateAppearance ());
            msh.SetFeedbackBuffer (fbuf);
            msh.SetVertexBuffer (tvbuf);
            msh.SetAppearance (CreateTransformAppearance (msh));

            return msh;
        }

        protected override void OnLoad (System.EventArgs e) {

            var msh = CreateSkinnedMesh ();

            var nodMesh = new Node ();
            nodMesh.Attach (msh);
            nodMesh.Translate (0, -5, 0);

            var bone0 = new Node ();
            bone0.UserID = 100;
            nodMesh.AddChild (bone0);

            var bone1 = new Node ();
            bone1.UserID = 101;
            bone1.Translate (0, 5, 0);
            bone0.AddChild (bone1);

            msh.SetBones (new Node[] { bone0, bone1 });

            var cam = new Camera ();
            cam.SetPerspective (45, Width / (float)Height, 0.1f, 100f);

            var nodCam = new Node ();
            nodCam.Attach (cam);
            nodCam.SetTranslation (0, 0, 20);

            wld = new World ();
            wld.AddChild (nodMesh);
            wld.AddChild (nodCam);
            wld.SetActiveCamera (nodCam);

            g3d = Graphics3D.GetInstance ();
            g3d.RenderPass += delegate {
                g3d.SetWorld (wld);
                g3d.SetClearColor (0, 0, 1, 1);
                g3d.SetClearDepth (1);
                g3d.SetColorClearEnabled (true);
                g3d.SetDepthClearEnabled (true);
                g3d.SetViewort (0, 0, Width, Height);
            };


            // 未実装
            VSync = VSyncMode.On;
        }

        protected override void OnUnload (EventArgs e) {
            wld = null;
            GC.Collect ();
            g3d.Release ();
        }

        protected override void OnUpdateFrame (FrameEventArgs e) {
            var bone1 = wld.Find (101) as Node;
            if (bone1 != null) {
                bone1.Rotate (1, 0, 0, 1);
            }
        }

        protected override void OnRenderFrame (FrameEventArgs e) {

            g3d.Render ();

            SwapBuffers ();
        }

        /*
        [STAThread]
        public static void Main () {
            using (var example = new Program5 ()) {
                example.Run (30);
            }
        }
        */
    }
}
